/*
 *  Copyright 2007 Blandware (http://www.blandware.com)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.blandware.atleap.antext.core;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.Reference;
import org.hibernate.FlushMode;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.orm.hibernate3.SessionFactoryUtils;
import org.springframework.orm.hibernate3.SessionHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;

/**
 * <p>Abstract Ant task that works on DB.</p>
 * <p><a href="AbstractDbTask.java.html"><i>View source</i></a></p>
 *
 * @author Roman Puchkovskiy <a href="mailto:roman.puchkovskiy@blandware.com">
 *         &lt;roman.puchkovskiy@blandware.com&gt;</a>
 * @version $Revision: 0.1 $ $Date: 2006/06/15 10:52:36 $
 */
public abstract class AbstractDbTask extends Task {
	/**
	 * Default name of session factory bean
	 */
	public static final String DEFAULT_SESSION_FACTORY_BEAN_NAME = "sessionFactory";

    /**
     * Application context
     */
    protected ApplicationContext applicationContext;
    /**
     * Session factory
     */
    protected SessionFactory sessionFactory;
    /**
     * Name of bean by which session factory is obtained
     */
    protected String sessionFactoryBeanName = DEFAULT_SESSION_FACTORY_BEAN_NAME;
    /**
     * Session
     */
    protected Session session;
    /**
     * Path containing Spring XML configuration files which define context
     */
    protected Path configPath = null;

    /**
     * Executes task.
     *
     * @throws BuildException   thrown if something is wrong
     */
    public void execute() throws BuildException {
		if ( configPath == null ) {
			throw new BuildException("Missing Spring configuration file(s). Please specify either configPathRef attribute or nested <config> element.");
		}

        // set context class loader for this thread
		Thread.currentThread().setContextClassLoader(getClass().getClassLoader());

		// configure application context
		String[] configLocations = configPath.list();
		applicationContext = new FileSystemXmlApplicationContext(configLocations);

        //opening session
        sessionFactory = lookupSessionFactory();
        session = null;
        boolean participate = false;
        if ( TransactionSynchronizationManager.hasResource(sessionFactory) ) {
            // do not modify the Session: just set the participate flag
            participate = true;
        } else {
            session = getSession(sessionFactory);
            TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
        }

        // doing job
        try {
            doExecute();
        } catch (Exception e) {
            BuildException be;
            if (e instanceof BuildException) {
                be = (BuildException) e;
            } else {
                be = new BuildException(e);
            }
            throw be;
        }

        //closing session
		if ( !participate ) {
			TransactionSynchronizationManager.unbindResource(sessionFactory);
			closeSession(session, sessionFactory);
		}
    }

    /**
     * Executes task job assuming that session is already initialized.
     *
     * @throws Exception    if any error occures
     */
    protected abstract void doExecute() throws Exception;

    /**
	 * Looks up the SessionFactory that class filter should use.
	 * <p>The default implementation looks for a bean with the specified name
	 * in Spring's root application context.
	 *
	 * @return the SessionFactory to use
	 * @see #getSessionFactoryBeanName
	 */
	protected SessionFactory lookupSessionFactory() {
		return (SessionFactory) applicationContext.getBean(getSessionFactoryBeanName());
	}

	/**
	 * Gets a Session for the SessionFactory that this class uses.
	 * Note that this just applies in single session mode!
	 * <p>The default implementation delegates to SessionFactoryUtils'
	 * getSession method and sets the Session's flushMode to NEVER.
	 * <p>Can be overridden in subclasses for creating a Session with a custom
	 * entity interceptor or JDBC exception translator.
	 *
	 * @param sessionFactory the SessionFactory that this listener uses
	 * @return the Session to use
	 * @throws org.springframework.dao.DataAccessResourceFailureException
	 *          if the Session could not be created
	 * @see org.springframework.orm.hibernate3.SessionFactoryUtils#getSession(org.hibernate.SessionFactory, boolean)
	 * @see org.hibernate.FlushMode#MANUAL
	 */
	protected Session getSession(SessionFactory sessionFactory)
			throws DataAccessResourceFailureException {
		Session session = SessionFactoryUtils.getSession(sessionFactory, true);
		session.setFlushMode(FlushMode.MANUAL);
		return session;
	}

	/**
	 * Closes the given Session.
	 * Note that this just applies in single session mode!
	 * <p>The default implementation delegates to SessionFactoryUtils'
	 * closeSessionIfNecessary method.
	 * <p>Can be overridden in subclasses, e.g. for flushing the Session before
	 * closing it. See class-level javadoc for a discussion of flush handling.
	 * Note that you should also override getSession accordingly, to set
	 * the flush mode to something else than NEVER.
	 *
	 * @param session        the Session used for filtering
	 * @param sessionFactory the SessionFactory that this filter uses
	 */
	protected void closeSession(Session session, SessionFactory sessionFactory) {
		SessionFactoryUtils.releaseSession(session, sessionFactory);
	}

    /**
	 * Sets the bean name of the SessionFactory to fetch from Spring's
	 * root application context. Default is "sessionFactory".
	 *
	 * @param sessionFactoryBeanName    bean name
     * @see #DEFAULT_SESSION_FACTORY_BEAN_NAME
	 */
	public void setSessionFactoryBeanName(String sessionFactoryBeanName) {
		this.sessionFactoryBeanName = sessionFactoryBeanName;
	}

	/**
	 * Returns the bean name of the SessionFactory to fetch from Spring's
	 * root application context.
     *
     * @return session factory bean name
	 */
	protected String getSessionFactoryBeanName() {
		return sessionFactoryBeanName;
	}

    /**
     * Sets reference to path containing Spring context configuration files.
     *
     * @param configPathRef     reference
     */
    public void setConfigPathRef(Reference configPathRef) {
		this.configPath = new Path(getProject());
		configPath.setRefid(configPathRef);
	}
}